Beheers de User Timing API om aangepaste, betekenisvolle prestatiemetrics te creƫren. Ga verder dan standaard web vitals om knelpunten op te sporen en de gebruikerservaring te optimaliseren.
Frontend Prestaties Meesteren: Een Diepgaande Blik op de User Timing API
In het moderne digitale landschap zijn frontend prestaties geen luxe; het is een fundamentele vereiste voor succes. Voor een wereldwijd publiek kan een trage, niet-responsieve website leiden tot frustratie bij de gebruiker, verminderde betrokkenheid en een directe negatieve impact op bedrijfsresultaten. We hebben uitstekende gestandaardiseerde metrics zoals Core Web Vitals (Largest Contentful Paint, First Input Delay, Cumulative Layout Shift) die ons een basisinzicht geven in de gebruikerservaring. Hoewel deze metrics cruciaal zijn, vertellen ze slechts een deel van het verhaal.
Hoe zit het met de prestaties van applicatie-specifieke functies? Hoe lang duurt het voordat zoekresultaten verschijnen nadat een gebruiker een zoekopdracht intypt? Hoeveel tijd kost het voor je complexe datavisualisatiecomponent om te renderen na het ontvangen van data van een API? Hoe beïnvloedt een nieuwe functie de snelheid van de routetransities van je single-page application (SPA)? Standaard metrics kunnen deze granulaire, bedrijfskritische vragen niet beantwoorden. Dit is waar de User Timing API van pas komt, die ontwikkelaars in staat stelt om aangepaste, uiterst precieze prestatiemetingen te creëren die zijn afgestemd op hun unieke applicaties.
Deze uitgebreide gids leidt je door alles wat je moet weten om de User Timing API te benutten, van de basisconcepten van markeringen en metingen tot geavanceerde technieken met de PerformanceObserver. Aan het einde ben je uitgerust om verder te gaan dan generieke metrics en het unieke prestatieverhaal van jouw applicatie te vertellen.
Wat is de Performance API? Een Bredere Context
Voordat we diep in User Timing duiken, is het belangrijk te begrijpen dat het deel uitmaakt van een grotere suite van tools die gezamenlijk bekend staan als de Performance API. Deze browser-API biedt toegang tot uiterst precieze timingdata met betrekking tot navigatie, het laden van resources en meer. Het globale `window.performance`-object is je toegangspunt tot deze krachtige toolset.
De Performance API bestaat uit verschillende interfaces, waaronder:
- Navigation Timing: Biedt gedetailleerde timinginformatie over het documentnavigatieproces, zoals de tijd besteed aan DNS-lookups, TCP-handshakes en het ontvangen van de eerste byte.
- Resource Timing: Biedt gedetailleerde netwerktimingdata voor elke resource die door de pagina wordt geladen, inclusief afbeeldingen, scripts en CSS-bestanden.
- Paint Timing: Stelt timings voor First Paint en First Contentful Paint beschikbaar.
- User Timing: De focus van ons artikel, die ontwikkelaars in staat stelt hun eigen aangepaste tijdstempels (markeringen) te creƫren en de duur daartussen te meten (metingen).
Deze API's werken samen om een holistisch beeld te geven van de prestaties van je applicatie. Ons doel vandaag is om het User Timing-gedeelte onder de knie te krijgen, wat ons de kracht geeft om onze eigen aangepaste checkpoints aan deze prestatietijdlijn toe te voegen.
De Kernconcepten: Markeringen en Metingen
De User Timing API is bedrieglijk eenvoudig en draait om twee fundamentele concepten: markeringen en metingen. Zie het als het gebruik van een stopwatch. Je drukt op een knop om een starttijd te markeren, en je drukt er nogmaals op om een eindtijd te markeren. De duur tussen die twee momenten is je meting.
Prestatiemarkeringen Creƫren: `performance.mark()`
Een 'markering' (mark) is een benoemde, hoge-resolutie tijdstempel die op een specifiek punt in de uitvoering van je applicatie wordt vastgelegd. Het is alsof je een vlag plant op je prestatietijdlijn. Je kunt zoveel markeringen maken als je nodig hebt om sleutelmomenten in een gebruikerstraject of de levenscyclus van een component te identificeren.
De syntaxis is eenvoudig:
performance.mark(markName, [markOptions]);
markName: Een string die de unieke naam voor je markering vertegenwoordigt. Kies beschrijvende namen!markOptions(optioneel): Een object dat eendetail-eigenschap kan bevatten voor het toevoegen van extra metadata, en eenstartTimeom een aangepaste tijdstempel op te geven.
Basisvoorbeeld: Een Gebeurtenis Markeren
Stel, we willen het begin van een belangrijke functieaanroep markeren.
function processLargeDataset() {
// Plaats een markering net voordat het zware werk begint
performance.mark('processLargeDataset:start');
// ... zware computationele logica ...
console.log('Dataset processing complete.');
// Plaats nog een markering als het klaar is
performance.mark('processLargeDataset:end');
}
processLargeDataset();
In dit voorbeeld hebben we twee tijdstempels gemaakt in de prestatietijdlijn van de browser: `processLargeDataset:start` en `processLargeDataset:end`. Op dit moment zijn het slechts punten in de tijd. Hun ware kracht wordt ontsloten wanneer we ze gebruiken om een meting te creƫren.
Context Toevoegen met de `detail`-eigenschap
Soms is een tijdstempel alleen niet genoeg. Misschien wil je extra context toevoegen over wat er op dat moment gebeurde. De `detail`-eigenschap is hier perfect voor. Het kan alle data bevatten die structureel gekloond kan worden (zoals objecten, arrays, strings, getallen).
Stel je voor dat we de start van het renderen van een component markeren en willen weten hoeveel items het aan het renderen was.
function renderProductList(products) {
const itemCount = products.length;
performance.mark('ProductList:render:start', {
detail: {
itemCount: itemCount,
source: 'initial-load'
}
});
// ... logica voor het renderen van de component ...
performance.mark('ProductList:render:end');
}
const sampleProducts = new Array(1000).fill(0);
renderProductList(sampleProducts);
Deze extra context is van onschatbare waarde bij het later analyseren van prestatiegegevens. Je zou bijvoorbeeld rendertijden kunnen correleren met het aantal items om te zien of er een lineair of exponentieel verband is.
Prestatiemetingen Creƫren: `performance.measure()`
Een 'meting' (measure) legt de duur tussen twee tijdstippen vast. Het is de berekening die je vertelt "hoe lang" iets duurde. Meestal meet je de tijd tussen twee van je aangepaste markeringen.
De syntaxis heeft een paar variaties:
performance.measure(measureName, startMarkOrOptions, [endMark]);
measureName: Een string die de unieke naam voor je meting vertegenwoordigt.startMarkOrOptions(optioneel): Een string met de naam van de startmarkering. Kan ook een optie-object zijn met `start`, `end`, `duration` en `detail`.endMark(optioneel): Een string met de naam van de eindmarkering.
Basisvoorbeeld: De Duur van een Functie Meten
Laten we voortbouwen op ons `processLargeDataset`-voorbeeld en daadwerkelijk meten hoe lang het duurde.
function processLargeDataset() {
performance.mark('processLargeDataset:start');
// ... zware computationele logica ...
performance.mark('processLargeDataset:end');
// Maak nu de meting aan
performance.measure(
'processLargeDataset:duration',
'processLargeDataset:start',
'processLargeDataset:end'
);
}
processLargeDataset();
Nadat deze code is uitgevoerd, bevat de performance buffer van de browser een nieuwe registratie met de naam `processLargeDataset:duration`. Deze registratie heeft een `duration`-eigenschap die de precieze tijd in milliseconden bevat die is verstreken tussen de start- en eindmarkeringen.
Geavanceerde Meetscenario's
De `measure()`-methode is erg flexibel. Je hoeft niet altijd twee markeringen op te geven.
- Van Navigatiestart tot een Markering: Je kunt de tijd meten vanaf het begin van de paginanavigatie tot een van je aangepaste markeringen. Dit is ongelooflijk handig voor het meten van zaken als "Tijd tot Interactief Component".
// Meet vanaf de start van de navigatie tot de hoofdcomponent gereed is performance.measure('timeToInteractiveHeader', 'navigationStart', 'headerComponent:ready'); - Van een Markering tot Nu: Als je de `endMark` weglaat, wordt de meting berekend vanaf je `startMark` tot het huidige moment.
// Meet vanaf de startmarkering tot deze coderegel wordt uitgevoerd performance.measure('timeSinceDataRequest', 'api:fetch:start'); - Het Optie-object Gebruiken: Je kunt ook een configuratieobject doorgeven om de meting te definiƫren, wat handig is voor het toevoegen van een `detail`-eigenschap.
performance.measure('complexRender:duration', { start: 'complexRender:start', end: 'complexRender:end', detail: { renderType: 'canvas' } });
Prestatieregistraties Ophalen en Wissen
Het creƫren van markeringen en metingen is slechts de helft van het werk. Je hebt een manier nodig om deze gegevens op te halen om ze te analyseren. Het `performance`-object biedt hiervoor verschillende methoden.
performance.getEntries(): Geeft een array terug van alle prestatieregistraties in de buffer (inclusief resource timings, navigation timings, etc.).performance.getEntriesByType(type): Geeft een array terug van registraties van een specifiek type. Je zult meestal `performance.getEntriesByType('mark')` en `performance.getEntriesByType('measure')` gebruiken.performance.getEntriesByName(name, [type]): Geeft een array terug van registraties met een specifieke naam (en optioneel, een specifiek type).
Voorbeeld: Metingen naar de Console Loggen
// Na het uitvoeren van onze vorige voorbeelden...
const allMeasures = performance.getEntriesByType('measure');
console.log(allMeasures);
// Een meting-registratieobject ziet er ongeveer zo uit:
// {
// "name": "processLargeDataset:duration",
// "entryType": "measure",
// "startTime": 12345.67,
// "duration": 150.89
// }
const specificMeasure = performance.getEntriesByName('processLargeDataset:duration');
console.log(`Processing took: ${specificMeasure[0].duration}ms`);
Belangrijk: De Performance Buffer Opschonen
De performance buffer van de browser is niet oneindig. Om geheugenlekken te voorkomen en je metingen relevant te houden, is het een best practice om de markeringen en metingen die je hebt gemaakt te wissen zodra je er klaar mee bent.
performance.clearMarks([name]): Wist alle markeringen, of alleen markeringen met de opgegeven naam.performance.clearMeasures([name]): Wist alle metingen, of alleen metingen met de opgegeven naam.
Een veelvoorkomend patroon is om de gegevens op te halen, te verwerken of te verzenden, en ze vervolgens te wissen.
function analyzeAndClear() {
const myMeasures = performance.getEntriesByName('processLargeDataset:duration');
// Stuur myMeasures naar een analytics-service...
sendToAnalytics(myMeasures);
// Ruim op om geheugen vrij te maken
performance.clearMarks('processLargeDataset:start');
performance.clearMarks('processLargeDataset:end');
performance.clearMeasures('processLargeDataset:duration');
}
Praktische, Real-World Toepassingen voor User Timing
Nu we de mechanica begrijpen, laten we onderzoeken hoe we de User Timing API kunnen toepassen om echte prestatie-uitdagingen op te lossen. Deze voorbeelden zijn framework-agnostisch en kunnen worden aangepast aan elke frontend-stack.
1. De Duur van API-aanroepen Meten
Begrijpen hoe lang je applicatie op data wacht is cruciaal. Je kunt je data-fetching logica eenvoudig omwikkelen met markeringen en metingen.
async function fetchUserData(userId) {
const markStart = `api:getUser:${userId}:start`;
const markEnd = `api:getUser:${userId}:end`;
const measureName = `api:getUser:${userId}:duration`;
performance.mark(markStart);
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return await response.json();
} catch (error) {
console.error('Fetch error:', error);
// Je kunt zelfs details over fouten toevoegen!
performance.mark(markEnd, { detail: { status: 'error', message: error.message } });
} finally {
// Zorg ervoor dat de eindmarkering en meting altijd worden aangemaakt
if (performance.getEntriesByName(markEnd).length === 0) {
performance.mark(markEnd, { detail: { status: 'success' } });
}
performance.measure(measureName, markStart, markEnd);
}
}
fetchUserData('123');
Dit patroon biedt precieze timings voor elke API-aanroep, waardoor je trage endpoints direct kunt identificeren op basis van echte gebruikersdata.
2. Rendertijden van Componenten in SPA's Volgen
Voor frameworks zoals React, Vue of Angular is het meten van de tijd die een component nodig heeft om te mounten en te renderen een primaire toepassing. Dit helpt bij het identificeren van complexe componenten die je applicatie mogelijk vertragen.
Voorbeeld met React Hooks:
import React, { useLayoutEffect, useEffect, useRef } from 'react';
function MyHeavyComponent({ data }) {
const componentId = useRef(`MyHeavyComponent-${Math.random()}`).current;
const markStartName = `${componentId}:render:start`;
const markEndName = `${componentId}:render:end`;
const measureName = `${componentId}:render:duration`;
// useLayoutEffect wordt synchroon uitgevoerd na alle DOM-mutaties.
// Het is de perfecte plek om het begin van de rendermeting te markeren.
useLayoutEffect(() => {
performance.mark(markStartName);
}, []); // Voer alleen uit bij de eerste mount
// useEffect wordt asynchroon uitgevoerd nadat de render naar het scherm is weggeschreven.
// Dit is een goede plek om het einde te markeren.
useEffect(() => {
performance.mark(markEndName);
performance.measure(measureName, markStartName, markEndName);
// Log het resultaat ter demonstratie
const measure = performance.getEntriesByName(measureName)[0];
if (measure) {
console.log(`${measureName} took ${measure.duration}ms`);
}
// Opruimen
performance.clearMarks(markStartName);
performance.clearMarks(markEndName);
performance.clearMeasures(measureName);
}, []); // Voer alleen uit bij de eerste mount
return (
// ... JSX voor de zware component ...
);
}
3. Kritieke Gebruikerstrajecten Kwantificeren
Het meest impactvolle gebruik van User Timing is het meten van meerstaps-gebruikersinteracties die cruciaal zijn voor je bedrijf. Dit overstijgt eenvoudige technische metrics en meet de waargenomen snelheid van de kernfunctionaliteit van je applicatie.
Denk aan een afrekenproces in e-commerce:
const checkoutButton = document.getElementById('checkout-btn');
checkoutButton.addEventListener('click', () => {
// 1. Gebruiker klikt op de 'afrekenen'-knop
performance.mark('checkout:journey:start');
// ... code om winkelwagen te valideren, naar betaalpagina te navigeren, enz. ...
});
// Op de betaalpagina, nadat het betaalformulier is gerenderd en interactief is
function onPaymentFormReady() {
performance.mark('checkout:paymentForm:ready');
performance.measure('checkout:timeToPaymentForm', 'checkout:journey:start', 'checkout:paymentForm:ready');
}
// Nadat de betaling succesvol is verwerkt en het bevestigingsscherm wordt getoond
function onPaymentSuccess() {
performance.mark('checkout:journey:end');
performance.measure('checkout:totalJourney:duration', 'checkout:journey:start', 'checkout:journey:end');
// Nu heb je twee krachtige metrics om te analyseren en te optimaliseren.
}
4. Prestatieverbeteringen A/B-testen
Wanneer je een stuk code refactort of een nieuw algoritme introduceert, hoe bewijs je dan dat het daadwerkelijk sneller is voor echte gebruikers? User Timing levert objectieve data voor A/B-testen.
Stel je voor dat je twee verschillende sorteeralgoritmen wilt testen:
function sortProducts(products, algorithmVersion) {
const markStart = `sort:v${algorithmVersion}:start`;
const markEnd = `sort:v${algorithmVersion}:end`;
const measureName = `sort:v${algorithmVersion}:duration`;
performance.mark(markStart);
if (algorithmVersion === 'A') {
// ... voer het oude sorteeralgoritme uit ...
} else {
// ... voer het nieuwe, geoptimaliseerde sorteeralgoritme uit ...
}
performance.mark(markEnd);
performance.measure(measureName, markStart, markEnd);
}
// Op basis van een A/B-testvlag zou je de een of de ander aanroepen.
// Later kun je in je analytics de gemiddelde duur van
// 'sort:vA:duration' vergelijken met 'sort:vB:duration' om te zien welke sneller was.
Je Aangepaste Metrics Visualiseren en Analyseren
Het creƫren van aangepaste metrics is zinloos als je de data niet analyseert. Er zijn twee primaire manieren om dit aan te pakken: lokaal tijdens de ontwikkeling en geaggregeerd in productie.
Browser Developer Tools Gebruiken
Moderne browsers zoals Chrome en Firefox hebben uitstekende ondersteuning voor het visualiseren van User Timing-markeringen en -metingen in hun performance profiling tools.
- Open de Developer Tools van je browser (F12 of Ctrl+Shift+I).
- Ga naar het Performance-tabblad.
- Start de opname van een profiel en voer vervolgens de acties in je app uit die je aangepaste markeringen en metingen activeren.
- Stop de opname.
In de tijdlijnweergave vind je een speciale rij genaamd Timings. Je aangepaste markeringen verschijnen als verticale lijnen, en je metingen worden weergegeven als gekleurde balken die hun duur tonen. Als je eroverheen zweeft, worden hun namen en exacte timings onthuld. Dit is een ongelooflijk krachtige manier om prestatieproblemen tijdens de ontwikkeling te debuggen.
Data Versturen naar Analytics en RUM-services
Voor productiemonitoring moet je deze data van je gebruikers verzamelen en naar een centrale locatie sturen voor aggregatie en analyse. Dit is een kernonderdeel van Real User Monitoring (RUM).
De algemene workflow is:
- Verzamel de prestatiemetingen waarin je geĆÆnteresseerd bent.
- Formatteer ze in een geschikte payload (bijv. JSON).
- Stuur de payload naar een analytics-endpoint. Dit kan een externe service zijn zoals Datadog, New Relic, Sentry, of zelfs Google Analytics (via aangepaste gebeurtenissen), of een aangepaste backend die je zelf beheert.
function sendPerformanceData() {
// We zijn alleen geĆÆnteresseerd in onze aangepaste applicatiemetingen
const appMeasures = performance.getEntriesByType('measure').filter(
(entry) => entry.name.startsWith('app:') // Gebruik een naamgevingsconventie!
);
if (appMeasures.length > 0) {
const payload = JSON.stringify(appMeasures.map(measure => ({
name: measure.name,
duration: measure.duration,
startTime: measure.startTime,
details: measure.detail, // Stuur onze rijke context mee
path: window.location.pathname // Voeg meer context toe
})));
// Gebruik navigator.sendBeacon voor betrouwbare, niet-blokkerende dataverzending
navigator.sendBeacon('https://analytics.example.com/performance', payload);
// Ruim de verzonden metingen op
appMeasures.forEach(measure => {
performance.clearMeasures(measure.name);
// Wis ook de bijbehorende markeringen
});
}
}
// Roep deze functie op een geschikt moment aan, bijv. wanneer de pagina op het punt staat te worden verlaten
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
sendPerformanceData();
}
});
Geavanceerde Technieken en Best Practices
Om de User Timing API echt onder de knie te krijgen, kijken we naar enkele geavanceerde functies en best practices die je instrumentatie robuuster en efficiƫnter zullen maken.
`PerformanceObserver` Gebruiken voor Asynchrone Monitoring
De `getEntries*()`-methoden vereisen dat je de performance buffer handmatig bevraagt (polling). Dit heeft twee nadelen: je kunt je controle te laat uitvoeren en registraties missen als de buffer vol is geraakt en is gewist, en het pollen zelf kan een kleine prestatiekost hebben. De moderne, geprefereerde oplossing is de `PerformanceObserver`.
Een `PerformanceObserver` stelt je in staat om je te abonneren op performance-registratiegebeurtenissen. Je callback-functie wordt asynchroon aangeroepen wanneer nieuwe registraties van de types die je observeert worden vastgelegd.
// 1. Maak een callback-functie om nieuwe registraties te verwerken
const observerCallback = (list) => {
for (const entry of list.getEntries()) {
console.log('Nieuwe meting geobserveerd:', entry.name, entry.duration);
// Hier kun je de registratie onmiddellijk naar je analytics-service sturen
// zonder te hoeven pollen of wachten.
}
};
// 2. Maak de observer-instantie aan
const observer = new PerformanceObserver(observerCallback);
// 3. Begin met observeren voor 'mark'- en 'measure'-registratietypes
// De 'buffered: true'-optie zorgt ervoor dat je registraties ontvangt die zijn gemaakt
// *voordat* de observer werd geregistreerd.
observer.observe({ entryTypes: ['mark', 'measure'], buffered: true });
// Nu, elke keer dat performance.mark() of performance.measure() ergens wordt aangeroepen
// in je applicatie, wordt de observerCallback geactiveerd met de nieuwe registratie.
// Om later te stoppen met observeren:
// observer.disconnect();
Het gebruik van `PerformanceObserver` is efficiƫnter, betrouwbaarder en zou je standaardkeuze moeten zijn voor het verzamelen van prestatiegegevens in een productieomgeving.
Een Duidelijke Naamgevingsconventie Vaststellen
Naarmate je applicatie groeit, zul je veel aangepaste metrics verzamelen. Zonder een consistente naamgevingsconventie worden je gegevens moeilijk te filteren en te analyseren. Adopteer een patroon dat context biedt.
Een goede conventie zou kunnen zijn: [appName]:[featureOrComponent]:[eventName]:[status]
ecom:ProductGallery:render:startecom:ProductGallery:render:endecom:ProductGallery:render:durationadmin:DataTable:fetchApi:startadmin:DataTable:fetchApi:duration
Deze structuur maakt het triviaal om te filteren op alle metrics gerelateerd aan de `ProductGallery` of om alle `fetchApi`-duren in de hele applicatie te vinden.
Abstraheren naar een Utility Service
Om consistentie te garanderen en herhalende code te verminderen, verpak je de `performance`-aanroepen in je eigen utility-module of service. Dit maakt het ook gemakkelijk om prestatiemonitoring in of uit te schakelen op basis van de omgeving.
// performance-service.js
const IS_PERFORMANCE_MONITORING_ENABLED = process.env.NODE_ENV === 'production' || window.location.search.includes('perf=true');
export const perfMark = (name, options) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.mark(name, options);
};
export const perfMeasure = (name, start, end) => {
if (!IS_PERFORMANCE_MONITORING_ENABLED) return;
performance.measure(name, start, end);
};
export const startJourney = (name) => {
perfMark(`${name}:start`);
};
export const endJourney = (name) => {
const startMark = `${name}:start`;
const endMark = `${name}:end`;
const measureName = `${name}:duration`;
perfMark(endMark);
perfMeasure(measureName, startMark, endMark);
// Wis hier optioneel de markeringen
};
// In je component:
// import { startJourney, endJourney } from './performance-service';
// startJourney('ecom:checkout');
// ...later...
// endJourney('ecom:checkout');
Conclusie: Neem de Controle over het Prestatieverhaal van je Applicatie
Hoewel standaard metrics zoals Core Web Vitals een essentiƫle gezondheidscheck voor je website bieden, belichten ze niet de prestaties van de functies en interacties die jouw applicatie uniek maken. De User Timing API is de brug die deze kloof dicht. Het biedt een eenvoudig maar zeer krachtig mechanisme om te meten wat echt belangrijk is voor je gebruikers en je bedrijf.
Door aangepaste markeringen en metingen te implementeren, transformeer je prestatie-optimalisatie van een gokspel naar een data-gedreven wetenschap. Je kunt de exacte functies, componenten of gebruikerstrajecten aanwijzen die knelpunten veroorzaken, de impact van je refactoring-inspanningen valideren met objectieve cijfers, en uiteindelijk een snellere, aangenamere ervaring bouwen voor je wereldwijde publiek.
Begin klein. Identificeer het meest kritieke gebruikerstraject in je applicatieāof het nu gaat om het zoeken naar een product, het indienen van een formulier, of het laden van een data-dashboard. Instrumenteer het met `performance.mark()` en `performance.measure()`. Analyseer de resultaten in je developer tools. Zodra je de helderheid ziet die het biedt, ben je in staat om het volledige prestatieverhaal van je applicatie te vertellen, ƩƩn aangepaste metric per keer.